﻿
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using DataLoadingModule.Model;
using DataLoadingModule.View;
using DataLoadingModule.ViewModel.LoadDataSet;
using Microsoft.Win32;
using SharedResources.Model;
using SharedResources.ViewModel;

namespace DataLoadingModule.ViewModel
{
    public class DatasetWizardViewModel : BaseViewModel
    {
        //Setup stuff
        private readonly DatasetModel _datasetModel = new DatasetModel(); //instantiate the model data

        #region Wizard Functionality

        //Wizard functionality

        private static Window _datasetWizardWindow; //can only be one wizard at a time

        public void ShowWizard()
        {
            _datasetWizardWindow = new LoadDatasetWizard();
                //cant have this in constructor otherwiser stackoverflow exception
            _datasetWizardWindow.Show(); //load the new dataWizard window
            _datasetWizardWindow.Topmost = true;
            _datasetWizardWindow.MouseLeave += delegate {
                                                       _datasetWizardWindow.Topmost = false;
                                                   };
        }




        private void ResetWizard() //reset the wizard because of some event (new dataset, changed delimiter etc)
        {
            _lineToStopAt = 1; //so the read next line knows how far to go
            _analysedValueIndex = 0; //so the value being loaded will be the first
            _datasetModel.AvailableDataTypes = null; //when it is set to null it reintialises
            ComboboxDataTypes = _datasetModel.AvailableDataTypes; //reset the available datatypes
            _datasetModel.LockedInDatasetOrder = new Dictionary<int, string>(); //reset the dictionary
            OnPropertiesChanged("ComboboxDataTypes", "SelectedComboboxItem");
            //reset the combo box for datatypes
            
        }

        private void OnPropertiesChanged(params string[] propertiesChanged)
        {
            foreach (var property in propertiesChanged)
            {
                OnPropertyChanged(property);
            }
        }

        #endregion

        #region Read First Line of Data Set

        //Read the dataset

        private string _singleLineDataset;

        public string SingleLineDataset
        {
            get
            {
                if (string.IsNullOrEmpty(_singleLineDataset))
                {
                    return "No trajectory file to read yet...";
                }
                return _singleLineDataset;
            }
        }

        private int _lineToStopAt = 1; //gets incremented when the user presses the read line button

        private string ReadDatasetLine(string datasetFilePath)
        {
            if (string.IsNullOrEmpty(datasetFilePath))
            {
                return "Can't read the chosen data set, the filepath is empty.";
            }
            var datasetFile = new StreamReader(datasetFilePath); //make a file reader

            string singleLine = string.Empty; //the line to get outputted
            for (int i = 0; i < _lineToStopAt; i++)
            {
                datasetFile.ReadLine(); //read a line
                if (i == _lineToStopAt - 1)
                {
                    singleLine = datasetFile.ReadLine(); //when it is at the last line in the range
                    //set the line to the output value
                }
            }
            datasetFile.Close(); //close it otherwise can get memory problems and data popping in
            
            if (string.IsNullOrEmpty(singleLine) && _lineToStopAt != 1)
            {
                //if the line being read was completely blank
                _lineToStopAt = 1;
                return ReadDatasetLine(datasetFilePath);
            }
            if(string.IsNullOrEmpty(singleLine) && _lineToStopAt == 1)
            {
                return "The whole file or atleast the first line is empty";
            }
            return singleLine;
        }

        private bool _filePathOkay;

        private void PopulateFirstDatasetLine()
            //logic used to read and populate the gui wtih the firstline from the dataset
        {
            if (_filePathOkay)
            {
                _singleLineDataset = ReadDatasetLine(_datasetModel.DatasetPath);
                OnPropertyChanged("SingleLineDataset");
                var tempLineToStopAt = _lineToStopAt; //need this because it gets wiped next line
                ResetWizard();
                _lineToStopAt = tempLineToStopAt + 1; //increment the stopping line by one
                DelimitFirstLine();
            }
        }

        private RelayCommand _readAnotherLine;

        public ICommand ReadAnotherLine
        {
            get { return _readAnotherLine = MakeCommandSafely(_readAnotherLine, () => PopulateFirstDatasetLine()); }
        }

        #endregion

        #region Delimiter and Value analysed

        //private string DelimitedDataset;
        public char DatasetDelimiter
        {
            get { return _datasetModel.Delimeter; }
            set
            {
                if(_datasetModel.Delimeter != value) //is same do nothing
                {
                    _datasetModel.Delimeter = value;
                    ResetWizard(); //changing the delimiter means a reset is required
                    DelimitFirstLine(); //redelimit and update the value being analysed
                }
            }
        }

        private string _valueBeingAnalysed;

        public string ValueBeingAnalysed
        {
            get
            {
                if (_valueBeingAnalysed == null)
                {
                    return "No value to analyse.";
                }
                if(_datasetModel.LockedInDatasetOrder != null 
                    && _datasetModel.LockedInDatasetOrder.ContainsKey(_analysedValueIndex))
                {
                    return _datasetModel.LockedInDatasetOrder[_analysedValueIndex] + ": " +
                                          _valueBeingAnalysed;
                }
                return "No datatype: " + _valueBeingAnalysed;
            }
        }

        private string GenerateValuesAroundAnalysed(string directionToLook)
        {
            //function that gets and edits the values either side of the
            //value currently being analysed
            //it Takes either "BACKWARD" or "FORWARD" as arguments
            var generatedValues = string.Empty;
            const string datatypeSuffix = "No datatype: ";
            int startIndex;
            int stopIndex;
            
            //annonymous inner function to get the value if it exists and appends a ': ' to it.
             Func<int, string> TryGetDatatype = index => 
            { 
                if (_datasetModel.LockedInDatasetOrder.ContainsKey(index))
                {
                    return _datasetModel.LockedInDatasetOrder[index] + ": ";
                } 
                return datatypeSuffix; 
            };

            switch(directionToLook)
            {
                case "FORWARD":
                    startIndex = _analysedValueIndex + 1;
                    stopIndex = _delimitedFirstLine.Length - 1;
                    if(startIndex <= stopIndex)
                    {
                        for (int i = startIndex; i < stopIndex + 1; i++)
                        {
                            generatedValues += (" " + TryGetDatatype(i) + _delimitedFirstLine.GetValue(i) + "\n");
                        }
                    }
                    break;
                case "BACKWARD":
                    startIndex = _analysedValueIndex -1;
                    stopIndex = 0;
                    if (startIndex >= stopIndex)
                    {
                        for (int i = startIndex; i >= stopIndex; i--)
                        {
                            generatedValues += (" " + TryGetDatatype(i) + _delimitedFirstLine.GetValue(i) + "\n");
                        }
                    }
                    break;
            }
            return generatedValues;
        }

        private string _valuesBeforeAnalysed;
        public string ValuesBeforeAnalysed
        {
            get
            {
                if(_valuesBeforeAnalysed == null)
                {
                    return "...";
                }
                return _valuesBeforeAnalysed;
                
            }
        }

        private string _valuesAfterAnalysed;
        public string ValuesAfterAnalysed
        {
            get
            {
                if (_valuesAfterAnalysed == null)
                {
                    return "...";
                }
                return _valuesAfterAnalysed;
            }
        }

        private string[] _delimitedFirstLine = {};
        private int _analysedValueIndex;

        private void DelimitFirstLine()
        {
            string[] delimitedLine = _singleLineDataset.Split(_datasetModel.Delimeter);
            if (delimitedLine.Length > 1)
            {
                //set the gui elem of value being analysed
                _delimitedFirstLine = delimitedLine;
                UpdateValueToAnalyse("START");
            }
            else
            {
                //else set the value being analysed to say can't delimit this line
                _valueBeingAnalysed =
                    "Could not delimit the dataset. Try reloading the file, reading the next line, or changing the delimiter.";
                //set show the help message which is above^^^
                OnPropertyChanged("ValueBeingAnalysed"); //update the gui
            }
        }

        private void UpdateValueToAnalyse(string updateInstruction)
        {
            //is a bool so that the previous and next button are disabled if returns false
            //either START, NEXT, PREVIOUS
            if (_delimitedFirstLine.Length <= 1) return; //do nothing
            switch (updateInstruction)
            {
                case "START":
                    _analysedValueIndex = 0;
                    _valueBeingAnalysed = _delimitedFirstLine[_analysedValueIndex];
                    _valuesBeforeAnalysed = GenerateValuesAroundAnalysed("BACKWARD");
                    _valuesAfterAnalysed = GenerateValuesAroundAnalysed("FORWARD");
                    OnPropertiesChanged("ValuesAfterAnalysed", "ValuesBeforeAnalysed", "ValueBeingAnalysed");
                    //update the gui
                    break;
                case "NEXT":
                        _analysedValueIndex++;
                        _valueBeingAnalysed = _delimitedFirstLine[_analysedValueIndex];
                        _valuesBeforeAnalysed = GenerateValuesAroundAnalysed("BACKWARD");
                        _valuesAfterAnalysed = GenerateValuesAroundAnalysed("FORWARD");
                        OnPropertiesChanged("ValuesBeforeAnalysed", "ValuesAfterAnalysed",
                            "ValueBeingAnalysed", "SelectedComboboxItem", "ComboboxDataTypes");
                        //update the gui
                        //and redraw the combobox
                    break;
                case "PREVIOUS":
                        _analysedValueIndex--;
                        _valueBeingAnalysed = _delimitedFirstLine[_analysedValueIndex];
                        _valuesBeforeAnalysed = GenerateValuesAroundAnalysed("BACKWARD");
                        _valuesAfterAnalysed = GenerateValuesAroundAnalysed("FORWARD");
                        OnPropertiesChanged("ValuesBeforeAnalysed", "ValuesAfterAnalysed",
                            "ValueBeingAnalysed", "SelectedComboboxItem", "ComboboxDataTypes");
                    break;
            }
        }

        #endregion

        #region Previous and Next Button

        private bool CanUpdateValue(string updateInstruction)
        {
            //is a bool so that the previous and next button are disabled if returns false
            //either START, NEXT, PREVIOUS
            if (_delimitedFirstLine.Length <= 1) return false; //do nothing
            switch (updateInstruction)
            {
                case "NEXT":
                    if (_analysedValueIndex + 1 <= _delimitedFirstLine.Length - 1){return true;}
                    break;
                case "PREVIOUS":
                    if (_analysedValueIndex - 1 >= 0){return true;}
                    break;
            }
            return false;
        }

        private RelayCommand _previousValueCommand;
        public ICommand PreviousValueCommand
        {
            get
            {
                return _previousValueCommand = MakeCommandSafely(_previousValueCommand, () => UpdateValueToAnalyse("PREVIOUS"), () => CanUpdateValue("PREVIOUS"));
            }
        }

        private RelayCommand _nextValueCommand;
        public ICommand NextValueCommand
        {
            get
            {
                return _nextValueCommand = MakeCommandSafely(_nextValueCommand, () => UpdateValueToAnalyse("NEXT"), () => CanUpdateValue("NEXT"));
            }
        }

        #endregion

        #region Combobox

        //Combobox
        public ObservableCollection<string> ComboboxDataTypes
        {
            get
            {
                if(_datasetModel.AvailableDataTypes != null && CanunapplyDatatypeFromValue() 
                    && _datasetModel.LockedInDatasetOrder.ContainsKey(_analysedValueIndex))
                {
                    //unapply is available and there is available types then only display one value
                    //and also the value being analysed is within the indexing bounds of all possible datatype 
                    //(i.e 8 datatypes and 9 values cannot work)

                    string datatypeToDisplay = _datasetModel.LockedInDatasetOrder[_analysedValueIndex];
                    //get the datatype to display
                    return new ObservableCollection<string>
                               {
                                   datatypeToDisplay
                                   //_datasetModel.AvailableDataTypes[_datasetModel.AvailableDataTypes.IndexOf(datatypeToDisplay)]
                                   //get the same reference to it and assign it
                               };
                }

                if (_datasetModel.AvailableDataTypes != null && _datasetModel.AvailableDataTypes.Count == 0)
                {
                    string[] noDatatypes = { "ALL DATATYPES ARE SELECTED" };
                    return new ObservableCollection<string>(noDatatypes);
                }

                return _datasetModel.AvailableDataTypes; //else display all the values
            }
            set { _datasetModel.AvailableDataTypes = value; }
        }

        private string _selectedComboboxItem;
        public string SelectedComboboxItem
        {
            get
            {
                if(_selectedComboboxItem == null 
                    || _selectedComboboxItem.Equals("") 
                    || ComboboxDataTypes.Contains(_selectedComboboxItem) == false)
                {
                    _selectedComboboxItem = ComboboxDataTypes[0]; //return the first data type
                }
                return _selectedComboboxItem;
            }
            set
            {
                if (_selectedComboboxItem == value) return;
                _selectedComboboxItem = value;
            }
        }

        #endregion

        #region Apply/Unapply Datatype

        //Apply Datatype button

        private void ApplyDatatypeToValue()
        {
            if(CanApplyDatatypeToValue()) //have to put this here to disable hotkeys
            {
                _datasetModel.LockedInDatasetOrder.Add(_analysedValueIndex, SelectedComboboxItem);
                OnPropertiesChanged("ComboboxDataTypes", "ValueBeingAnalysed");
                //update the combobox values
                //update the gui
                var selectedDatatypeIndex = _datasetModel.AvailableDataTypes.IndexOf(SelectedComboboxItem);
                _datasetModel.AvailableDataTypes.RemoveAt(selectedDatatypeIndex); //remove the applied type
            }
        }

        private bool CanApplyDatatypeToValue()
        {
            if (_datasetModel.LockedInDatasetOrder == null || _delimitedFirstLine.Length == 0){return false;}
            if(_datasetModel.LockedInDatasetOrder.ContainsKey(_analysedValueIndex)){return false;}
            if (ComboboxDataTypes[0].Equals("ALL DATATYPES ARE SELECTED")) { return false; }
            if (_delimitedFirstLine[_analysedValueIndex].Equals(_valueBeingAnalysed)){return true;}
            return false;
        }

        private RelayCommand _applyDatatypeCommand;
        public ICommand ApplyDatatypeCommand
        {
            get
            {
                return _applyDatatypeCommand = MakeCommandSafely(_applyDatatypeCommand, () => ApplyDatatypeToValue(), () => CanApplyDatatypeToValue());
            }
        }

        //UnapplyButton

        private void UnapplyDatatypeFromValue()
        {
            var dataTypeToBringBack = _datasetModel.LockedInDatasetOrder[_analysedValueIndex];
            _datasetModel.LockedInDatasetOrder.Remove(_analysedValueIndex);
            _datasetModel.AvailableDataTypes.Add(dataTypeToBringBack);
            SelectedComboboxItem = _datasetModel.AvailableDataTypes[_datasetModel.AvailableDataTypes.Count - 1];
            OnPropertiesChanged("SelectedComboboxItem", "ValueBeingAnalysed");//update gui
        }

        private bool CanunapplyDatatypeFromValue()
        {
            if(_datasetModel.LockedInDatasetOrder == null || _datasetModel.LockedInDatasetOrder.Count == 0){return false;}
            
            Func<bool> canApplyValue = () =>
                                      {
                                          if (_datasetModel.LockedInDatasetOrder == null || _delimitedFirstLine.Length == 0) { return false; }
                                          if (_datasetModel.LockedInDatasetOrder.ContainsKey(_analysedValueIndex)) { return false; }
                                          if (_delimitedFirstLine[_analysedValueIndex].Equals(_valueBeingAnalysed)) { return true; }
                                          return false;
                                      };

            if (canApplyValue() == false) { return true; }

            return false;
        }

        private RelayCommand _unapplyDatatypeCommand;
        public ICommand UnapplyDatatypeCommand
        {
            get
            {
                return _unapplyDatatypeCommand = MakeCommandSafely(_unapplyDatatypeCommand, () => UnapplyDatatypeFromValue(), () => CanunapplyDatatypeFromValue());
            }
        }

        #endregion

        #region FinishWizard

        private void FinishWizard()
        {
            var dataTypeValidator = new DataTypeValidator(_datasetModel.LockedInDatasetOrder, _delimitedFirstLine);
            if(dataTypeValidator.ValidateData())
            {
                var popUpProgressBar = new PopUpProgressBarViewModel();
                popUpProgressBar.Show(popUpProgressBar, "Transforming Raw Data");
                var worker = new BackgroundWorker { WorkerReportsProgress = true };

                worker.ProgressChanged += delegate(object s, ProgressChangedEventArgs args)
                {
                    if (args.ProgressPercentage != popUpProgressBar.Progresser)
                    {
                        popUpProgressBar.Progresser = args.ProgressPercentage;
                        popUpProgressBar.Details = args.ProgressPercentage.ToString(CultureInfo.InvariantCulture) + "%";
                    }
                };

                worker.RunWorkerCompleted += (s, args) => //when its done
                                                 {
                                                     popUpProgressBar.ClosePopUp();
                                                     _datasetWizardWindow.Close();
                                                 };

                worker.DoWork += delegate
                {
                    var datasetParser = new DatasetParser(_datasetModel.LockedInDatasetOrder, _datasetModel.DatasetPath);
                    var sortedGroupedData = datasetParser.TryParse(_datasetModel.Delimeter, worker); //data is parsed and bounds are set
                    var selectableDataset = new SelectableDataset(sortedGroupedData)
                                                {
                                                    WillVisualise = true
                                                };

                    Application.Current.Dispatcher.Invoke //perform the action on gui(main) thread
                    (
                        DispatcherPriority.Normal,
                        (Action) (() => SharedResources.Utilities.UnderlyingData.AddToDataSets(selectableDataset.DataLabel,selectableDataset))
                    );
                };

                try
                {
                    worker.RunWorkerAsync();
                }
                catch (Exception e)
                {
                    MessageBox.Show("The data could not be parsed." + e.Message);
                }
            }
        }

        private bool CanFinishWizard()
        {
            if(_delimitedFirstLine != null && _datasetModel.LockedInDatasetOrder != null)
            {
                if (_datasetModel.LockedInDatasetOrder.Count == _datasetModel.AllDataTypes.Count)
                { 
                    //if the datatypes are all selected
                    return true;
                }
            }
            return false;
        }

        private RelayCommand _finishWizardCommand;
        public ICommand FinishWizardCommand
        {
            
            get
            {
                //var wrapper = new Action(FinishWizard);
                return _finishWizardCommand = MakeCommandSafely(_finishWizardCommand, () => FinishWizard(), () => CanFinishWizard());
            }
        }

    #endregion 

        #region Filebrowser functionality

        //Filebrowser functionality

        public string DatasetFilePath
        {
            get { return _datasetModel.DatasetPath; }
        }

        private void DatasetBrowserFileOk(object sender, System.ComponentModel.CancelEventArgs e)
        {
            ResetWizard(); //this event means the wizard needs resetting
            var dialog = (OpenFileDialog)sender;
            _datasetModel.DatasetPath = dialog.FileName; //fileok event fired reset file path
            OnPropertyChanged("DatasetFilePath"); //tell the gui that property has changed
            _filePathOkay = true; //now a file can have its next line read
            PopulateFirstDatasetLine();
            DelimitFirstLine();
        }

        private void BrowseForDataset()
        {
            var dialog = new OpenFileDialog();
            //Now set the file type
            dialog.Filter = "Text files (*.txt)|*.txt|Comma Seperated Values (*.csv)|*.csv|All files (*.*)|*.*";
            dialog.InitialDirectory = "C:";
            dialog.Title = "Select the dataset to be loaded"; //Set the starting directory and the title.
            dialog.FileOk += DatasetBrowserFileOk; //fire an event to open a new window when okay file is selected
            dialog.ShowDialog(); //load the filebrwoser window
        }

        private RelayCommand _openDatasetBrowser;
        public ICommand OpenDatasetBrowser
        {
            get
            {
                return _openDatasetBrowser = MakeCommandSafely(_openDatasetBrowser, () => BrowseForDataset());
            }
        }

        #endregion
    }
}
